package com.jiexinyun.sftp.jsch;

import com.jcraft.jsch.*;
import com.jiexinyun.utils.CommonUtils;
import lombok.Data;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.*;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

@Component
public class SftpUtil {
    private static GenericObjectPool<ChannelSftp> sftpPool;
    private static SftpProperties sftpProperties;

    @Autowired
    public SftpUtil(SftpProperties properties) {
        sftpProperties = properties;
    }

    @PostConstruct
    public void init() {

        GenericObjectPoolConfig<ChannelSftp> config = new GenericObjectPoolConfig<>();
        config.setMaxTotal(sftpProperties.getPool().getMaxTotal());
        config.setMinIdle(sftpProperties.getPool().getMinIdle());
        config.setMaxIdle(sftpProperties.getPool().getMaxIdle());
        config.setMaxWait(Duration.ofSeconds(sftpProperties.getPool().getMaxWait()));
        SftpChannelFactory factory = new SftpChannelFactory(
                sftpProperties.getHost(),
                sftpProperties.getPort(),
                sftpProperties.getUsername(),
                sftpProperties.getPassword(),
                sftpProperties.getPool().getSessionTimout()
        );

        sftpPool = new GenericObjectPool<>(factory, config);
    }

    @PreDestroy
    public void destroy() {
        if (sftpPool != null && !sftpPool.isClosed()) {
            sftpPool.close();
        }
    }

    /**
     * 上传文件到SFTP服务器
     */
    public static void upload(String remotePath, String remoteFileName, InputStream input) throws Exception {
        ChannelSftp sftp = sftpPool.borrowObject();
        try {
            mkdirs(sftp, remotePath);
            sftp.cd(remotePath);
            sftp.put(input, remoteFileName);
        } finally {
            sftpPool.returnObject(sftp);
            if (input != null) {
                input.close();
            }
        }
    }

    /**
     * 从SFTP服务器下载文件
     */
    public static void download(String remotePath, String remoteFileName, OutputStream output) throws Exception {
        ChannelSftp sftp = sftpPool.borrowObject();
        try {
            sftp.cd(remotePath);
            sftp.get(remoteFileName, output);
        } finally {
            sftpPool.returnObject(sftp);
            if (output != null) {
                output.close();
            }
        }
    }

    /**
     * 上传文件到SFTP服务器
     */
    public static InputStream getInputStream(String remotePath, String remoteFileName) throws Exception {
        ChannelSftp sftp = sftpPool.borrowObject();
        try {
            String remoteFilePath = remotePath + "/" + remoteFileName;
            return sftp.get(remoteFilePath);
        } finally {
            sftpPool.returnObject(sftp);
        }
    }

    /**
     * 创建SFTP路径（支持多级目录创建）
     */
    public static void mkdirs(String directory) throws Exception {
        ChannelSftp sftp = sftpPool.borrowObject();
        try {
            mkdirs(sftp, directory);
        } finally {
            sftpPool.returnObject(sftp);
        }
    }

    private static void mkdirs(ChannelSftp sftp, String directory) throws SftpException {
        String[] folders = directory.split("/");
        sftp.cd("/");
        for (String folder : folders) {
            if (folder.length() > 0) {
                try {
                    sftp.cd(folder);
                } catch (SftpException e) {
                    sftp.mkdir(folder);
                    sftp.cd(folder);
                }
            }
        }
    }

    /**
     * 删除SFTP文件
     */
    public static void deleteFile(String remotePath, String remoteFileName) throws Exception {
        ChannelSftp sftp = sftpPool.borrowObject();
        try {
            sftp.cd(remotePath);
            sftp.rm(remoteFileName);
        } finally {
            sftpPool.returnObject(sftp);
        }
    }

    /**
     * 删除SFTP目录（递归删除）
     */
    public static void deleteDirectory(String remotePath) throws Exception {
        ChannelSftp sftp = sftpPool.borrowObject();
        try {
            deleteDirectory(sftp, remotePath);
        } finally {
            sftpPool.returnObject(sftp);
        }
    }

    private static void deleteDirectory(ChannelSftp sftp, String remotePath) throws SftpException {
        Vector<ChannelSftp.LsEntry> files = sftp.ls(remotePath);
        for (ChannelSftp.LsEntry entry : files) {
            if (!entry.getFilename().equals(".") && !entry.getFilename().equals("..")) {
                String filePath = remotePath + "/" + entry.getFilename();
                if (entry.getAttrs().isDir()) {
                    deleteDirectory(sftp, filePath);
                } else {
                    sftp.rm(filePath);
                }
            }
        }
        sftp.rmdir(remotePath);
    }

    /**
     * 列出SFTP目录下的文件
     */
    public List<String> listFiles(String remotePath) throws Exception {
        ChannelSftp sftp = sftpPool.borrowObject();
        try {
            Vector<ChannelSftp.LsEntry> files = sftp.ls(remotePath);
            List<String> fileList = new ArrayList<>();
            for (ChannelSftp.LsEntry entry : files) {
                if (!entry.getFilename().equals(".") && !entry.getFilename().equals("..")) {
                    fileList.add(entry.getFilename());
                }
            }
            return fileList;
        } finally {
            sftpPool.returnObject(sftp);
        }
    }

    private static class SftpChannelFactory extends BasePooledObjectFactory<ChannelSftp> {

        private String host;

        private int port;

        private String username;

        private String password;

        private int sessionTimout;

        private Session session;

        public SftpChannelFactory(String host, int port, String username, String password, int sessionTimout) {
            this.host = host;
            this.port = port;
            this.username = username;
            this.password = password;
            this.sessionTimout = sessionTimout;
        }

        @Override
        public ChannelSftp create() throws Exception {
            if (session == null || !session.isConnected()) {
                JSch jsch = new JSch();
                session = jsch.getSession(username, host, port);
                session.setPassword(password);
                session.setConfig("StrictHostKeyChecking", "no");
                session.connect(sessionTimout);
            }

            ChannelSftp channel = null;

            if(session.isConnected()) {
                channel = (ChannelSftp) session.openChannel("sftp");
                channel.connect();
            }
            return channel;
        }

        @Override
        public PooledObject<ChannelSftp> wrap(ChannelSftp channelSftp) {

            return new DefaultPooledObject<>(channelSftp);
        }

        @Override
        public boolean validateObject(PooledObject<ChannelSftp> p) {
            if(CommonUtils.isNull(p)) {
                return false;
            }
            ChannelSftp channel = p.getObject();
            if(CommonUtils.isNull(channel)) {
                return false;
            }
            return channel.isConnected() && !channel.isClosed();
        }

        @Override
        public void destroyObject(PooledObject<ChannelSftp> p) throws Exception {
            if(CommonUtils.isNotNull(p)){
                ChannelSftp channel = p.getObject();
                if (CommonUtils.isNotNull(channel) && channel.isConnected()) {
                    Session session = channel.getSession();
                    channel.disconnect();
                    if (CommonUtils.isNotNull(session) && session.isConnected()) {
                        session.disconnect();
                    }
                }
            }
        }
    }
}
